from datetime import datetime, timedelta, timezone
from calendar import timegm

from rest_framework.settings import settings
from rest_framework import HTTP_HEADER_ENCODING

from core.aai.jwt_backends import TokenBackend, JWTInvalidError, BaseAAIToken
from apps.app_global.configs_manager import global_configs


UTC = timezone.utc


class DictObjectWrapper:
    def __init__(self, d: dict):
        self._d = d

    def __getattr__(self, name):
        return self._d.get(name)

    def __setattr__(self, name, value):
        if name == '_d':
            super().__setattr__(name, value)
            return

        self._d[name] = value


JWT_SETTINGS = DictObjectWrapper(getattr(settings, 'PASSPORT_JWT', None))

AUTH_HEADER_TYPES = JWT_SETTINGS.AUTH_HEADER_TYPES
if not isinstance(AUTH_HEADER_TYPES, (list, tuple)):
    AUTH_HEADER_TYPES = (AUTH_HEADER_TYPES,)

AUTH_HEADER_TYPE_BYTES = set(
    h.encode(HTTP_HEADER_ENCODING)
    for h in AUTH_HEADER_TYPES
)


def make_utc(dt: datetime) -> datetime:
    if dt.tzinfo is None:      # 不带时区
        return dt.replace(tzinfo=UTC)

    return dt.astimezone(UTC)


def datetime_to_epoch(dt: datetime) -> int:
    return timegm(dt.utctimetuple())


def datetime_from_epoch(ts: float) -> datetime:
    return datetime.fromtimestamp(ts, UTC)


def build_token_backend() -> TokenBackend:
    verifying_key = global_configs.get(global_configs.ConfigName.AAI_JWT_VERIFYING_KEY.value)
    if not verifying_key and not verifying_key.strip(' '):
        raise JWTInvalidError(
            'The authentication public key of AAI JWT is not configured in the global configuration of the admin site')

    return TokenBackend(
        algorithm=JWT_SETTINGS.ALGORITHM, signing_key=JWT_SETTINGS.SIGNING_KEY,
        verifying_key=verifying_key,
        audience=JWT_SETTINGS.AUDIENCE, issuer=JWT_SETTINGS.ISSUER)


class Token(BaseAAIToken):
    """
    A class which validates and wraps an existing JWT
    """
    token_type = 'accessToken'
    lifetime = timedelta(hours=1, minutes=5)
    EXPIRATION_CLAIM = JWT_SETTINGS.EXPIRATION_CLAIM or 'exp'
    TOKEN_TYPE_CLAIM = JWT_SETTINGS.TOKEN_TYPE_CLAIM
    JTI_CLAIM = None  # 无效时，生成的token不包含此内容
    ISSUER_CLAIM = 'iss'

    def __init__(self, token, verify=True, backend: TokenBackend = None):
        """
        !!!! IMPORTANT !!!! MUST raise a TokenError with a user-facing error
        message if the given token is invalid, expired, or otherwise not safe
        to use.
        """
        token_backend = backend if backend else build_token_backend()
        super().__init__(token=token, backend=token_backend, verify=verify)
